package org.aspectsense.middleware;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.logging.Logger;

import org.aspectsense.middleware.gui.ControlPanel;
import org.aspectsense.middleware.helpers.PromptMessage;

import org.aspectsense.middleware.monitor.EventsManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceListener;

public final class Repository {
	//private static HashMap <String,ArrayList<ComponentContainer>>service2Providers = new HashMap<String, ArrayList<ComponentContainer>>();
	private static HashMap <String,LinkedList<ServiceWrapper>>services2Wrappers = new HashMap<String,LinkedList<ServiceWrapper>>();
	private static HashMap <String,ComponentContainer>components2Containers = new HashMap<String,ComponentContainer>();
	private static HashMap <String,UtilityQueue>services2Utilities = new HashMap <String,UtilityQueue>();
	static BundleContext bundleContext =null;
	private static final Logger logger = Logger.getLogger(Repository.class.getCanonicalName());

	//public static enum Mode {SERVICE_REGISTERED, SERVICE_UNREGISTERED}
	//private static enum ServiceState {AVAILABLE,UNAVAILABLE}

	//---------Thread-safe Singleton For Ensuring single instantiation of Repository
	public final static Repository INSTANCE = new Repository();
	private Repository()
	{
		// Exists only to defeat instantiation.
	}
	//---------------------------------------


	/**
	 * Update the repository data structures with the available components and services.
	 * The {@link EventsManager} will call this method each time a new component registers one or more
	 * services. The component2Containers hashmap is updated and new ServiceWrappers are created. 
	 * @param componentContainer A container created by {@link ServiceListener} for the component that
	 * Also the hashmap service2Utilities will be updated if the service is ready and can be provided.
	 * provides the service
	 */
	public static void update_Registered(ComponentContainer componentContainer)
	{
		String[] servicesIds = componentContainer.getProvidedServicesClassNames();
		LinkedList<ServiceWrapper> temp;

		//Update components2Containers Map
		String componentName = componentContainer.getProviderName();
		//Add a new record in the map (if already exists it will be replaced)
		components2Containers.put(componentName,componentContainer);
				
		//Update the services2Wrappers Map
		//Check if a record for the service already exist
		for (String sName:servicesIds)
		{
			//A service record already exists => there is another provider that provides the service
			if (services2Wrappers.get(sName)!=null)
			{
				temp = services2Wrappers.get(sName);
				ServiceWrapper sw = new ServiceWrapper (sName, componentContainer, componentContainer.getProvidedServiceUtilityFunction(sName), componentContainer.isEndUserService(sName));
				temp.add(sw);
				//TODO: Remove the following command, the state of a service will be turned to
				//ready only when the controller decides that its provider is resolved
				

				updateUtilities (sw,"add");
			}
			else //New service
			{
				//Create service wrapper
				ServiceWrapper sw = new ServiceWrapper (sName, componentContainer, componentContainer.getProvidedServiceUtilityFunction(sName), componentContainer.isEndUserService(sName));
				temp = new LinkedList<ServiceWrapper>();
				temp.add(sw);
				services2Wrappers.put(sName,temp);
				
				
				updateUtilities (sw,"add");

			}
		}
	}

	/**
	 * Update the repository tables after the provider component has been unregistered
	 * from the OSGi Registry
	 * We have to update the components2Containers hashmap, remove the ComponentContainer for that provider
	 * remove all the serviceWrappers that have been created for the provided services 
	 * and update the service2Wrappers hashmap as well as the service2Utilities hashmap.
	 * @param serviceProviderClass
	 */
	public static void update_Unregistered(Class<? extends Object> serviceProviderClass)
	{
		String[] services;
		LinkedList<ServiceWrapper> wrappersList;
		String providerName = serviceProviderClass.getCanonicalName();
		ComponentContainer container;
		Iterator <ServiceWrapper> itr;
		ServiceWrapper sw;
		//Find the container of the provider component
		container = components2Containers.get(providerName);
		//Get the services that were provided and have been unregistered
		//from the OSGi Registry and now need to be removed from AspectSense Repository
		services = container.getProvidedServicesClassNames();

		for (String s:services) //For each one of the service that were provided by the unregistered provider
		{
			if ((wrappersList=services2Wrappers.get(s))!=null) //Find their records in the services2Wrappers hashmap
			{
				//We have the linked list with all the ServiceWrappers for 
				//the type of service that we want to unregister. We have to find
				//only the ServiceWrappers that have been created for the Provider
				//that we are trying to unregister
				itr = wrappersList.iterator();
				while (itr.hasNext())
				{
					sw=itr.next();
					if (sw.getProvider()==container)
					{
						itr.remove();
						updateUtilities (sw, "remove");
						sw=null;
						if (wrappersList.isEmpty())
						{
							wrappersList=null;
							services2Wrappers.remove(s);
							break;
						}
					}
				}
			}	
		}
		//Finally remove the container from the map and set it to null to be collected by GC
		Object res = components2Containers.remove(providerName);
		if (res==null)
			System.out.println("It was null");
		container=null;
	}



	/**
	 * This method will update the services2Utilities hashmap
	 * whenever necessary
	 * @param serviceWrapper
	 * @param mode if add, a new utility value of a serviceWrapper 
	 * will be added if remove the ServiceWrapper will be deleted from the hashmap
	 */
	private static void updateUtilities (ServiceWrapper serviceWrapper, String mode)
	{
		UtilityQueue tempQ=null;

		//We maintain only ready services
		if (serviceWrapper.isAvailable())
		{
			//if there is already record for this service type
			if ((tempQ = services2Utilities.get(serviceWrapper.getServiceName()))!=null)
			{
				if (mode.compareTo("add")==0)
					tempQ.add(serviceWrapper);
				else if (mode.compareTo("remove")==0)
				{
					tempQ.remove(serviceWrapper);
					if (tempQ.isEmpty()){
						services2Utilities.remove(serviceWrapper.getServiceName());
					}
				}
			}
			else //There is no record for this service type
			{
				if (mode.compareTo("add")==0)
				{
					services2Utilities.put(serviceWrapper.getServiceName(), new UtilityQueue());
					services2Utilities.get(serviceWrapper.getServiceName()).add(serviceWrapper);
				}
				else if (mode.compareTo("remove")==0)
				{
					logger.info("Tried to remove ServiceWrapper from service2Utilities" +
					" without the existance of record in the hashmap for the specific service");
				}
			}
		}
	}
	
	public static void updateTree (){
		
		//if no components or services we have nothing to build..
		if (components2Containers.isEmpty() && services2Wrappers.isEmpty())
			return;
		else if (!components2Containers.isEmpty() && services2Wrappers.isEmpty())
		ControlPanel.updateTree (components2Containers, null);
		else if (components2Containers.isEmpty() && !services2Wrappers.isEmpty())
			ControlPanel.updateTree (null, services2Wrappers);
		else
			ControlPanel.updateTree (components2Containers, services2Wrappers);
	}

	/**
	 * For TESTING PURPOSES
	 */
	public static void printCurrentState() {

		StringBuffer info = new StringBuffer();
		Collection<LinkedList<ServiceWrapper>> wrapperLists;
		Iterator<UtilityQueue> utilitr;
		Collection<UtilityQueue> utilityLists;
		Iterator<LinkedList<ServiceWrapper>> itr;
		int cnt=1;
		LinkedList <ServiceWrapper> wrapperList;

		
		if ((!components2Containers.isEmpty())|| (!services2Wrappers.isEmpty()) || (!services2Utilities.isEmpty()))
			info.append("+++++++++++++++++++++++++++++++++ Repository +++++++++++++++++++++++++++++++++\n");
		else
			return;
		
		if (!components2Containers.isEmpty()){
			info.append(" Components -> ComponentContainers:\n");
			info.append(" ------------------------------------------\n");
			Collection<ComponentContainer> x = components2Containers.values();
			Iterator<ComponentContainer> i = x.iterator();
			ComponentContainer cc;
			while (i.hasNext()){
				cc = i.next();
				info.append(cc.getInfo());
				info.append("\n");
			}
		}

		if (!services2Wrappers.isEmpty()){
			info.append(" Services -> ServiceWrappers:\n");
			info.append(" ------------------------------------------\n");

			wrapperLists = services2Wrappers.values();
			itr = wrapperLists.iterator();

			while(itr.hasNext())
			{
				wrapperList = (LinkedList<ServiceWrapper>) itr.next();
				for (ServiceWrapper sw:wrapperList)
				{
					info.append(" Service Name:"+sw.getServiceName()+"\n Provided by:\n");
					info.append(sw.getProvider().getProviderName()+" \n");
					info.append(" State:"+sw.getState()+"\n");
					info.append(" Utility Function:"+sw.getUtilityFunction()+"\n");
					info.append(" Utility Value:"+sw.getUtility()+"\n");
					info.append("\n");
					cnt++;
				}
			}
		}
		if(!services2Utilities.isEmpty()){
			info.append(" Services -> Utilities:\n");
			info.append(" ------------------------------------------\n");


			utilityLists = services2Utilities.values();
			utilitr = utilityLists.iterator();

			while(utilitr.hasNext())
			{
				UtilityQueue utilityList = (UtilityQueue) utilitr.next();
				for (ServiceWrapper sw:utilityList)
				{
					info.append(" Service:"+sw.getServiceName()+", Utility:"+sw.getUtility()+"\n");
					info.append("\n");
				}
			}

		}
		if(info.length()>0)
		{
			//new PromptMessage().drawDialog(info.toString());
			System.out.println(info);
		}
		info = null;
		wrapperLists=null;
		wrapperList=null;
		itr=null;
		utilityLists=null;
		utilitr=null;
		cnt=0;

	}
	
	//Getter & Setter
	public static ComponentContainer getProvider (String name)
	{
		return components2Containers.get(name);
	}
	/**
	 * 
	 * @return a linkedlist with all the installed components
	 */
	public static LinkedList <ComponentContainer> getComponents ()
	{
		return new LinkedList <ComponentContainer>(components2Containers.values());
	}
	public static LinkedList<ComponentContainer> getServiceProviders (String service)
	{
		LinkedList<ServiceWrapper> sw = services2Wrappers.get(service);
		LinkedList <ComponentContainer> cc = new LinkedList <ComponentContainer> ();
		if (sw!=null)
		{
			Iterator <ServiceWrapper> itr=sw.iterator();
			ServiceWrapper s;
			while(itr.hasNext())
			{
				s = itr.next();
				
				cc.add(s.getProvider());
			}
		}
		return cc;
	}
	
	/**
	 * If there is a provider that provides the given service as an end-user service
	 * then it is considered as an end-user service
	 * @param service
	 * @return
	 */
	public static boolean isEndUserService (String service)
	{
		LinkedList<ServiceWrapper> sw = services2Wrappers.get(service);
		if (sw!=null)
		{
			Iterator <ServiceWrapper> itr=sw.iterator();
			ServiceWrapper s;
			while(itr.hasNext())
			{
				s = itr.next();
				if (s.isEndUserService())
					return true;
			}
		}
		return false;
	}
	
	/**
	 * @return The ServiceWrapper for the given service and provider
	 */
	public static ServiceWrapper getServiceWrapper (String service, ComponentContainer provider)
	{
		Iterator <ServiceWrapper> wrapperItr;
		LinkedList <ServiceWrapper> wrappers = services2Wrappers.get(service);
		wrapperItr = wrappers.iterator();
		ServiceWrapper sw;
		while (wrapperItr.hasNext())
		{
			sw = wrapperItr.next();
			if (sw.getProvider().equals(provider))
				return sw;
		}
		return null;
	}
	

}
